package com.juzhencms.apps.busi.service;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;

import com.juzhencms.apps.base.busi.service.ConfigService;
import com.juzhencms.apps.base.common.config.Config;
import com.juzhencms.apps.base.common.database.DBTable;
import com.juzhencms.apps.base.common.util.UnicodeReader;

import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class SensitiveKeywordsService extends CommonService{
	
	public boolean running=false;//设置标志位记录执行状态，避免重复执行
	
	@Autowired
	private ConfigService configService;
	
	/*//导入关键词文本文件，每行一个关键词，会自动忽略重复关键词
	public void importKeywords(String filePath,String categoryName ) throws IOException{
		//判断分类是否存在，若不存在则创建分类
		Map category=this.getJdbcDao().queryRow("select * from "+DBTable.sensitiveCategory+" where name=? and del_flag=0", new Object[] {categoryName});
		if(category==null) {
			category=new HashMap();
			category.put("name", categoryName);
			category.put("create_time", new Date());
			int tmpId=this.getJdbcDao().addObj(DBTable.sensitiveCategory, category);
			category.put("id", tmpId);
		}
		int categoryId=(int)category.get("id");
		
		//获取文件编码
		File file=new File(filePath);
		String charset=this.getFileCharset(file);
		
		//自定义read类,读文件时忽略bom头
		UnicodeReader read = new UnicodeReader(new FileInputStream(file), charset);
		BufferedReader reader=new BufferedReader(read);   
		String line;
		while ((line = reader.readLine()) != null) {
			//System.out.println(line);
			
			String keywordText=line.trim();
			if(!keywordText.equals("")) {
				Map tmpWord=this.getJdbcDao().queryRow("select * from "+DBTable.sensitiveKeywords+" where name=? and del_flag=0", new Object[] {keywordText});
				if(tmpWord==null) {
					Map keyword=new HashMap();
					keyword.put("name", keywordText);
					keyword.put("category_id", categoryId);
					keyword.put("create_time", new Date());
					this.getJdbcDao().addObj(DBTable.sensitiveKeywords, keyword);
				}	
			}	
			
		}
		read.close();
		reader.close();
	}
	
	//导入关键词文件时用于判断文件编码
	private String getFileCharset( File file ) {   
        String charset = "GBK";   
        byte[] first3Bytes = new byte[3];
        boolean checked=false;
        try {              
            BufferedInputStream bis = new BufferedInputStream( new FileInputStream( file ) );   
            bis.mark( 0 );   
            int read = bis.read( first3Bytes, 0, 3 );   
            if ( read == -1 ) return charset;   
            if ( first3Bytes[0] == (byte) 0xFF && first3Bytes[1] == (byte) 0xFE ) {   
                charset = "UTF-16LE";   
                checked = true;   
            }   
            else if ( first3Bytes[0] == (byte) 0xFE && first3Bytes[1] == (byte) 0xFF ) {   
                charset = "UTF-16BE";   
                checked = true;   
            }   
            else if ( first3Bytes[0] == (byte) 0xEF && first3Bytes[1] == (byte) 0xBB && first3Bytes[2] == (byte) 0xBF ) {   
                charset = "UTF-8";   
                checked = true;   
            }   
            bis.reset();   
            if ( !checked ) {    
                int loc = 0;   
                while ( (read = bis.read()) != -1 ) {   
                    loc++;   
                    if ( read >= 0xF0 ) break;   
                    if ( 0x80 <= read && read <= 0xBF ) // 单独出现BF以下的，也算是GBK   
                    break;   
                    if ( 0xC0 <= read && read <= 0xDF ) {   
                        read = bis.read();   
                        if ( 0x80 <= read && read <= 0xBF ) // 双字节 (0xC0 - 0xDF) (0x80   
                                                                        // - 0xBF),也可能在GB编码内   
                        continue;   
                        else break;   
                    }   
                    else if ( 0xE0 <= read && read <= 0xEF ) {// 也有可能出错，但是几率较小   
                        read = bis.read();   
                        if ( 0x80 <= read && read <= 0xBF ) {   
                            read = bis.read();   
                            if ( 0x80 <= read && read <= 0xBF ) {   
                                charset = "UTF-8";   
                                break;   
                            }   
                            else break;   
                        }   
                        else break;   
                    }   
                }   
            }   
            bis.close();   
        } catch ( Exception e ) {   
            e.printStackTrace();   
        }   
        return charset;   
    }*/
	
	/**
     * 敏感词匹配规则
     */
    public static final int MinMatchTYpe = 1;//最小匹配规则
    public static final int MaxMatchType = 2;//最大匹配规则
 
    /**
     * 敏感词集合
     */
    public static HashMap sensitiveWordMap;
 
    /**
     * 初始化敏感词库,构建DFA算法模型
     *
     * @param sensitiveWordSet 敏感词库
     */
    public synchronized void init() {
    	Set sensitiveWordSet = new HashSet<>();
    	List categoryList=this.getJdbcDao().querySql("select id from "+DBTable.sensitiveCategory+" where publish=1 and del_flag=0 limit 0,1000");
    	if(categoryList!=null) {
    		Map tempCategory=null;
    		List sensitiveWordList=null;
    		for(int i=0;i<categoryList.size();i++) {
    			tempCategory=(Map)categoryList.get(i);
    			sensitiveWordList=this.getJdbcDao().querySql("select name from "+DBTable.sensitiveKeywords+" where category_id=? and publish=1 and del_flag=0 limit 0,10000",new Object[] {tempCategory.get("id")});
    	    	if(sensitiveWordList!=null) {
    	    		Map tempKeyword=null;
    	        	for(int j=0;j<sensitiveWordList.size();j++) {
    	        		tempKeyword=(Map)sensitiveWordList.get(j);
    	        		sensitiveWordSet.add(tempKeyword.get("name"));
    	        	}
    	        }
    		}
    	}
    	
    	initSensitiveWordMap(sensitiveWordSet);
    }
    
    public Map getSensitiveWordMap() {
    	if(sensitiveWordMap==null)
    		init();
    	return sensitiveWordMap;
    }
 
    /**
     * 初始化敏感词库,构建DFA算法模型
     *
     * @param sensitiveWordSet 敏感词库
     */
    private static void initSensitiveWordMap(Set sensitiveWordSet) {
        //初始化敏感词容器,减少扩容操作
        sensitiveWordMap = new HashMap(sensitiveWordSet.size());
        String key;
        Map nowMap;
        Map newWorMap;
        //迭代sensitiveWordSet
        Iterator iterator = sensitiveWordSet.iterator();
        while (iterator.hasNext()) {
            //关键字
            key = String.valueOf(iterator.next());
            nowMap = sensitiveWordMap;
            for (int i = 0; i < key.length(); i++) {
                //转换成char型
                char keyChar = key.charAt(i);
                //库中获取关键字
                Object wordMap = nowMap.get(keyChar);
                //如果存在该key,直接赋值,用于下一个循环获取
                if (wordMap != null) {
                    nowMap = (Map) wordMap;
                } else {
                    //不存在则,则构建一个map,同时将isEnd设置为0,因为他不是最后一个
                    newWorMap = new HashMap<>();
                    //不是最后一个
                    newWorMap.put("isEnd", "0");
                    nowMap.put(keyChar, newWorMap);
                    nowMap = newWorMap;
                }
                if (i == key.length() - 1) {
                    //最后一个
                    nowMap.put("isEnd", "1");
                }
            }
        }
    }
 
    /**
     * 判断文字是否包含敏感字符
     *
     * 文本
     *  matchType 匹配规则 1:最小匹配规则,2:最大匹配规则
     *  若包含返回true,否则返回false
     */
    public boolean contains(String txt, int matchType) {
        boolean flag = false;
        for (int i = 0; i < txt.length(); i++) {
            int matchFlag = checkSensitiveWord(txt, i, matchType); //判断是否包含敏感字符
            if (matchFlag > 0) {//大于0存在,返回true
                flag = true;
            }
        }
        return flag;
    }
 
    /**
     * 判断文字是否包含敏感字符
     *
     * @param txt 文字
     * @return 若包含返回true,否则返回false
     */
    public boolean contains(String txt) {
        return contains(txt, MinMatchTYpe);
    }
 
    /**
     * 获取文字中的敏感词
     *
     *  txt文字
     *  matchType 匹配规则 1:最小匹配规则,2:最大匹配规则
     *
     */
    public List getSensitiveWord(String txt, int matchType) {
        List sensitiveWordList = new ArrayList();
        for (int i = 0; i < txt.length(); i++) {
            //判断是否包含敏感字符
            int length = checkSensitiveWord(txt, i, matchType);
            if (length > 0) {//存在,加入list中
            	String keyword=txt.substring(i, i + length);
            	if(!sensitiveWordList.contains(keyword)) {
            		sensitiveWordList.add(keyword);
            	}
                
                i = i + length - 1;//减1的原因,是因为for会自增
            }
        }
        return sensitiveWordList;
    }
 
    /**
     * 获取文字中的敏感词
     *
     * @param txt 文字
     * @return
     */
    public List getSensitiveWord(String txt) {
        return getSensitiveWord(txt, MaxMatchType);
    }
 
    /**
     * 检查文字中是否包含敏感字符,检查规则如下:
     *
     * @param txt
     * @param beginIndex
     * @param matchType
     * @return 如果存在,则返回敏感词字符的长度,不存在返回0
     */
    private int checkSensitiveWord(String txt, int beginIndex, int matchType) {
        //敏感词结束标识位:用于敏感词只有1位的情况
        boolean flag = false;
        //匹配标识数默认为0
        int matchFlag = 0;
        char word;
        Map nowMap = getSensitiveWordMap();
        for (int i = beginIndex; i < txt.length(); i++) {
            word = txt.charAt(i);
            //获取指定key
            nowMap = (Map) nowMap.get(word);
            if (nowMap != null) {//存在,则判断是否为最后一个
                //找到相应key,匹配标识+1
                matchFlag++;
                //如果为最后一个匹配规则,结束循环,返回匹配标识数
                if ("1".equals(nowMap.get("isEnd"))) {
                    //结束标志位为true
                    flag = true;
                    //最小规则,直接返回,最大规则还需继续查找
                    if (MinMatchTYpe == matchType) {
                        break;
                    }
                }
            } else {//不存在,直接返回
                break;
            }
        }
        if (matchFlag < 2 || !flag) {//长度必须大于等于1,为词
            matchFlag = 0;
        }
        return matchFlag;
    }
 
    
    /**
	 * 排查数据库记录的敏感词
	 * @param table 数据表名
	 * @param id 数据记录id
	 * @param force 是否强制排查，若为true，则不受敏感词开关影响
	 * @return
	 */
	public String filterSensitiveKeywords(String table,int id,boolean force) {
		//判断是否开启了关键词过滤功能
		String openfilterSensitive=configService.getValue("filter_sensitive");
		if(openfilterSensitive==null || !openfilterSensitive.equals("1")) {
			return null;
		}
		if(table==null || id==0) {
			return null;
		}
		
		Map data=this.getJdbcDao().queryRow("select * from "+table+" where id=?", new Object[] {id});
		String keywordsStr=this.filterSensitiveKeywords(table, data,force);
		if(keywordsStr==null) {
			return null;
		}else {
			return keywordsStr;
		}
	}
	/**
	 * 排查数据库记录的敏感词
	 * @param table 数据表名
	 * @param data 数据记录Map
	 * @param force 是否强制排查，若为true，则不受敏感词开关影响
	 * @return
	 */
	public String filterSensitiveKeywords(String table,Map data,boolean force) {
		
		if(!force) {
			//判断是否开启了关键词过滤功能
			String openfilterSensitive=configService.getValue("filter_sensitive");
			if(openfilterSensitive==null || !openfilterSensitive.equals("1")) {
				return null;
			}
		}
		
		StringBuffer sb=new StringBuffer();
		List keywords=new ArrayList();

		if(table.equals(DBTable.news)) {
			//新闻数据检查
			//列出需要排查的字段，第一个字段是名称或标题字段
			String[] fields= {"name","subname","keywords","description","content","author","source","seo_keywords","seo_description"};
			
			if(data==null)
				return null;
			for(int i=0;i<fields.length;i++) {
				if(data.get(fields[i])!=null) {
					String fieldText=data.get(fields[i]).toString();
					sb.append(fieldText+"|");
				}
			}
		}else if(table.equals(DBTable.article)){
			//文章数据检查
			String[] fields= {"description","keywords","content","seo_keywords","seo_description"};
			
			if(data==null)
				return null;
			for(int i=0;i<fields.length;i++) {
				if(data.get(fields[i])!=null) {
					String fieldText=data.get(fields[i]).toString();
					sb.append(fieldText+"|");
				}
			}	
		}else if(table.equals(DBTable.templateFile)){
			//文章数据检查
			String[] fields= {"content"};
			
			if(data==null)
				return null;
			for(int i=0;i<fields.length;i++) {
				if(data.get(fields[i])!=null) {
					String fieldText=data.get(fields[i]).toString();
					sb.append(fieldText+"|");
				}
			}	
		}
		
		String content=sb.toString();
		keywords=this.getSensitiveWord(content);
		
		if(keywords==null || keywords.size()==0) {
			return null;
		}else {
			return StringUtils.join(keywords.toArray(),",");
		}
	}
	
	/**
	 * 通过异步任务对站点数据进行排查并写入敏感词排查记录,此操作不受系统设置--敏感词开关的影响
	 * @return
	 * @throws InterruptedException
	 */
	@Async
	public void filterTask(){
		try {
			if(running) {
				return;
			}
			running=true;
			log.info("开始进行敏感词过滤");
			long begin = System.currentTimeMillis();
	        //Thread.sleep(2000L);
			//定义需要排查的数据表
			String[] tables= {DBTable.news,DBTable.article};
			//循环数据表
			for(int i=0;i<tables.length;i++) {
				String table=tables[i];
				//获取数据总量（未删除的）
				int count=this.getJdbcDao().querySqlForInt("select count(id) from "+table+" where del_flag=0");
				
				log.info(table+" total:"+count);
				
				//循环数据
				int page=0;
				int pagesize=50;
				for(int j=0;j<count;j=j+pagesize) {
					log.info(table+" index:"+j);
					List dataList=this.getJdbcDao().querySql("select * from "+table+" where del_flag=0 order by id asc limit ?,?", new Object[] {page*pagesize,pagesize});
					if(dataList!=null) {
						for(int k=0;k<dataList.size();k++) {
							Map data=(Map)dataList.get(k);
							String keywords=this.filterSensitiveKeywords(table, data,true);
							if(keywords!=null) {
								//记录到日志
								Map sensitiveLog=new HashMap();
								sensitiveLog.put("app", this.getConfig().getValue("app.appid"));
								
								
								
								
								if(data.get("menu_id")!=null) {
									Map menu=this.getJdbcDao().queryRow("select id,name,site_id from "+DBTable.menu+" where id=?",new Object[] {data.get("menu_id")});
									if(menu!=null) {
										sensitiveLog.put("menu_id", menu.get("id"));
										sensitiveLog.put("menu_name", menu.get("name"));
										Map site=this.getJdbcDao().queryRow("select id,name from "+DBTable.site+" where id=?",new Object[] {menu.get("site_id")});
										if(site!=null) {
											sensitiveLog.put("site_id", site.get("id"));
											sensitiveLog.put("site_name", site.get("name"));
										}
									}
								}
								
								
								if(keywords.length()>100) {
									keywords=keywords.substring(0, 97)+"...";
								}
								sensitiveLog.put("sensitive_keywords", keywords);
								
								
								sensitiveLog.put("data_type", table);
								sensitiveLog.put("data_id", data.get("id"));
								
								if(data.get("name")!=null) {
									sensitiveLog.put("data_title", data.get("name"));
								}else {
									sensitiveLog.put("data_title", sensitiveLog.get("menu_name"));
								}
								Date timenow=new Date();
								sensitiveLog.put("create_time", timenow);
								sensitiveLog.put("update_time", timenow);
								
								
								//判断是否存在，如果存在则更新
								Map oldLog=this.getJdbcDao().queryRow("select id from "+DBTable.sensitiveLog+" where status=0 and data_type=? and data_id=? limit 0,1", new Object[] {sensitiveLog.get("data_type"),sensitiveLog.get("data_id")});
								if(oldLog!=null) {
									sensitiveLog.put("id", oldLog.get("id"));
									this.getJdbcDao().updateObj(DBTable.sensitiveLog, sensitiveLog,"id");
								}else {
									this.getJdbcDao().addObj(DBTable.sensitiveLog, sensitiveLog);
								}
								
								
							}
						}
					}
					
					
					Thread.sleep(20L);
					page++;
				}
				
			}
			
	        
	        long end = System.currentTimeMillis();
	        log.info("执行完成，任务耗时="+(end-begin));
	        running=false;
		}catch(Exception e) {
			log.error("执行报错",e);
			running=false;
		}
		
		
        //return new AsyncResult<String>("任务完成");
    }   
	
	
}
